﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Concat
{
    [System.Diagnostics.DebuggerNonUserCode]
    class Scanner
    {
        string file;
        TextReader reader;
        int pos, line, col;
        Location curloc;
        char cur;
        bool eof;

        public Scanner(string file, TextReader reader, int line = 0)
        {
            this.file = file;
            this.reader = reader;
            pos = col = 0;
            this.line = line;
            eof = false;

            curloc = new Location(file, pos, line, col);

            int input = reader.Read();
            if (input < 0)
            {
                eof = true;
                cur = '\0';
            }
            else
            {
                cur = (char)input;
            }
        }

        [System.Diagnostics.DebuggerNonUserCode]
        void next()
        {
            int input = reader.Read();
            if (input < 0)
            {
                eof = true;
                cur = '\0';
            }
            else
            {
                updatePosition(cur);
                cur = (char)input;
            }
        }
        [System.Diagnostics.DebuggerNonUserCode]
        void updatePosition(char prev)
        {
            pos++;
            if (prev == '\n')
            {
                line++;
                col = 0;
            }
            else
            {
                col++;
            }
            curloc = new Location(file, pos, line, col);
        }

        [System.Diagnostics.DebuggerNonUserCode]
        bool whitespace(char c)
        {
            return c == ' ' || c == '\t' || c == '\n' || c == '\r';
        }
        [System.Diagnostics.DebuggerNonUserCode]
        bool numeric(char c)
        {
            return '0' <= c && c <= '9';
        }
        [System.Diagnostics.DebuggerNonUserCode]
        bool alpha(char c)
        {
            return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
        }
        [System.Diagnostics.DebuggerNonUserCode]
        bool alphanum(char c)
        {
            return alpha(c) || numeric(c);
        }

        [System.Diagnostics.DebuggerNonUserCode]
        bool identStart(char c)
        {
            return c == '_' || alpha(c);
        }
        //[System.Diagnostics.DebuggerNonUserCode]
        //bool identChar(char c)
        //{
        //    return c == '_' || c == '-' 
        //        || c == '?' || c == '/' || c == '!' 
        //        || c == ':' || c == '<' || c == '>'
        //        || alphanum(c);
        //}

        [System.Diagnostics.DebuggerNonUserCode]
        bool genericEnd(char c)
        {
            return eof
                || cur == '[' || cur == ']' 
                || cur == '(' || cur == ')'
                || cur == '"'// || cur == ':'
                //|| alphanum(cur)
                //|| numeric(cur)
                || whitespace(cur);
        }

        void skipComment()
        {
            while (!(eof || cur == '\n'))
                next();
            next();
        }
        void skipBlockComment()
        {
            char prevPrev = '\0', prev = '\0';
            while (!(eof || (prevPrev == ']' && prev == '~')))
            {
                prevPrev = prev;
                prev = cur;
                next();
            }
        }

        //[System.Diagnostics.DebuggerNonUserCode]
        public Token getToken()
        {
            // skip whitespace
            while (!eof && whitespace(cur))
                next();

            // location of start of token
            Location loc = curloc;

            if (eof) return new EOFToken(loc);

            // basic tokens
            switch (cur)
            {
                case '[': next(); return new LeftBracketToken(loc);
                case ']': next(); return new RightBracketToken(loc);
                case '(': next(); return new LeftParenToken(loc);
                case ')': next(); return new RightParenToken(loc);
                case '~': next(); if (cur == '[')
                    {
                        next();
                        skipBlockComment();
                        return getToken();
                    }
                    else
                    {
                        skipComment(); return getToken();
                    }
            }

            // delegate to other functions for more complex tokens
            if (numeric(cur)) return parseNumeric(loc);
            else if (cur == '"') return parseQuote(loc);
            else if (cur == ':') return parseSymbol(loc);
            //else if (identStart(cur)) return parseIdent(loc);
            else return parseGeneric(loc);
        }

        // integer = [0-9]+
        // number = [0-9]+.[0-9]*
        Token parseNumeric(Location loc)
        {
            string numstr = "" + cur;
            next();
            while (numeric(cur))
            {
                numstr += cur;
                next();
            }
            if (cur == '.')
            {
                numstr += cur;
                next();
                while (numeric(cur))
                {
                    numstr += cur;
                    next();
                }
                return new NumToken(numstr, loc);
            }
            else
                return new IntToken(numstr, loc);
        }

        // string = "([^\]|\[trn0])*"
        StrToken parseQuote(Location loc)
        {
            string text = "" + cur, data = "";
            next();

            while (!eof && cur != '"')
            {
                if (cur == '\\')
                {
                    text += cur;
                    next();
                    text += cur;
                    switch (cur)
                    {
                        case 't': data += '\t'; break;
                        case 'n': data += '\n'; break;
                        case 'r': data += '\r'; break;
                        case '0': data += '\0'; break;
                        default: data += cur; break;
                    }
                }
                else
                {
                    text += cur;
                    data += cur;
                }
                next();
            }
            if (eof)
                throw new UndelimitedStringException(loc, curloc);
            text += cur;
            next();

            return new StrToken(text, data, loc);
        }

        // symbol = :{{identifier}}
        SymToken parseSymbol(Location loc)
        {
            //string symbol = "";
            next();
            string ident = ((IdentToken)parseGeneric(curloc)).ident;
            return new SymToken(":" + ident, ident, loc);
            //if (!identStart(cur))
            //    throw new InvalidCharacterException(cur, loc);
            //symbol += cur;
            //next();
            //while (identChar(cur))
            //{
            //    symbol += cur;
            //    next();
            //}
            //return new SymToken(":" + symbol, symbol, loc);
        }

        //// ident = {{anything else}}
        //Token parseIdent(Location loc)
        //{
        //    // what should identifier be?
        //    // -- constraints? <-- current choice
        //    // -- or simply a catchall?
        //    // identifier - hi, name, identifier
        //    //   = [a-zA-Z_][a-zA-Z0-9_-/?!]*
        //    //   | `.*`
        //    //   | \[^ ]+

        //    string identstr = "" + cur;
        //    next();
        //    while (identChar(cur))
        //    {
        //        identstr += cur;
        //        next();
        //    }
        //    if (identstr == "def")
        //        return new DefToken(loc);
        //    return new IdentToken(identstr, identstr, loc);
        //}

        // basically, anything else
        IdentToken parseGeneric(Location loc)
        {
            string identstr = "" + cur;
            next();
            while (!(cur == '~' || genericEnd(cur)))
            {
                identstr += cur;
                next();
            }
            if (cur == '~')
                skipComment();
            if (identstr == "def")
                return new DefToken(loc);
            else if (identstr == "struct")
                return new StructToken(loc);
            else if (identstr == "extend-struct")
                return new ExtendStructToken(loc);
            else if (identstr == "method")
                return new DefMethodToken(loc);
            else if (identstr == "class")
                return new ClassToken(loc);
            else if (identstr == "extend-class")
                return new ExtendClassToken(loc);
            else if (identstr == "class-member")
                return new ClassMemberToken(loc);
            return new IdentToken(identstr, identstr, loc);
        }
    }
}
